
#include "file.h"
#include "config.h"
#include "memory/memory.h"
#include "memory/heap/kheap.h"
#include "textmode/textmode.h"
#include "status.h"
#include "fat/fat16.h"
#include "disk/disk.h"
#include "utils/string.h"
#include "kernel.h"

#include <stddef.h>


struct filesystem* filesystems[TESTOS_MAX_FILESYSTEMS_AMOUNT];
struct file_descriptor* file_descriptors[TESTOS_MAX_FILE_DESCRIPTORS_AMOUNT];

static struct filesystem** fs_get_free_filesystem() 
{
    size_t i;
    for (i = 0; i < TESTOS_MAX_FILESYSTEMS_AMOUNT; i++) {
        if (filesystems[i] == 0) {
            return &filesystems[i]; 
        }
    }

    return 0; 
}

static void fs_static_load() 
{
    fs_insert_filesystem(fat16_init()); 
}

static int file_new_descriptor(struct file_descriptor** desc_out)
{
    int res = -TESTOS_STATUS_NOMEMORY; 
    size_t i; 

    for (i = 0; i < TESTOS_MAX_FILE_DESCRIPTORS_AMOUNT; i++) {
        if (file_descriptors[i] == 0) {
            struct file_descriptor* desc = (struct file_descriptor*)kzalloc(sizeof(struct file_descriptor));

            // Descriptors start at 1 
            desc->index = i + 1; 
            file_descriptors[i] = desc; 
            *desc_out = desc; 
            res = 0;
            break; 
        }
    }

    return res; 
}

static int file_free_descriptor(struct file_descriptor* descriptor)
{
    file_descriptors[descriptor->index - 1] = 0x00; 
    kfree(descriptor); 
    return 0; 
}

static struct file_descriptor* file_get_descriptor(const int fd) 
{
    if (fd < 1 || fd > TESTOS_MAX_FILE_DESCRIPTORS_AMOUNT) {
        return 0; 
    }

    // Indexes start from 1, not 0
    return file_descriptors[fd - 1]; 
}

void fs_init()
{
    memset(file_descriptors, 0, sizeof(file_descriptors)); 
    memset(filesystems, 0, sizeof(filesystems)); 
    fs_static_load(); 
}

void fs_insert_filesystem(struct filesystem* filesystem)
{
    struct filesystem** fs;
    if (filesystem == 0) {
        // TODO: panic(); 
        print_colored("Problem inserting filesystem", TEXTMODE_COLOR_LIGHTRED); 
        while (1) { } 
    }

    fs = fs_get_free_filesystem(); 
    if (!fs) {
        // TODO: panic(); 
        print_colored("Problem inserting filesystem", TEXTMODE_COLOR_LIGHTRED); 
        while (1) { } 
    }

    *fs = filesystem; 
}

struct filesystem* fs_resolve(struct disk* disk)
{
    struct filesystem* fs = 0; 
    for (size_t i = 0; i < TESTOS_MAX_FILESYSTEMS_AMOUNT; i++) {
        fs = filesystems[i]; 
        if (fs != 0 && fs->resolve(disk) == 0) {
            return fs; 
        }
    }

    return 0; 
}

const FILE_MODE get_file_mode_from_string(const char* str) 
{
    FILE_MODE mode = FILE_MODE_INVALID; 
    if (strncmp(str, "r", 1) == 0) {
        mode = FILE_MODE_READ;
    }
    else if (strncmp(str, "w", 1) == 0) {
        mode = FILE_MODE_WRITE; 
    }
    else if (strncmp(str, "a", 1) == 0) {
        mode = FILE_MODE_APPEND; 
    }

    return mode; 
}

const int fopen(const char* filename, const char* mode_str)
{
    int res = 0; 
    struct path_root* root_path = pathparser_parse(filename, NULL);
    if (!root_path) {
        res = -TESTOS_STATUS_INVALID_ARG;
        goto out; 
    } 

    if (!root_path->first) { // check is path is like 0:/
        // we cannot have just a root path 
        res = -TESTOS_STATUS_INVALID_ARG;
        goto out; 
    }

    struct disk* disk = disk_get(root_path->drive_no); 
    if (!disk) { // ensure that disk with this number exists
        // if disk in path doen't exist
        res = -TESTOS_STATUS_IO_ERROR;
        goto out; 
    }

    if (!disk->filesystem) { // ensure that filesystem of the disk is one we can handle
        res = -TESTOS_STATUS_IO_ERROR;
        goto out; 
    }

    const FILE_MODE mode = get_file_mode_from_string(mode_str); 
    if (mode == FILE_MODE_INVALID) {
        res = -TESTOS_STATUS_INVALID_ARG; 
        goto out; 
    }

    void* descriptor_private_data = disk->filesystem->open(disk, root_path->first, mode); 
    if (ISERR(descriptor_private_data)) {
        res = ERROR_I(descriptor_private_data); 
        goto out; 
    }

    struct file_descriptor* descriptor = 0; 
    res = file_new_descriptor(&descriptor); 
    if (res < 0) {
        goto out; 
    }

    descriptor->filesystem = disk->filesystem; 
    descriptor->private = descriptor_private_data; 
    descriptor->disk = disk; 
    res = descriptor->index; 

out:
    if (res < 0) {
        res = 0; //fopen shouldn't return negative values
    }

    return res; 
}

const int fread(void* ptr, const uint32_t size, const uint32_t nmem_blocks, const int fd)
{
    int res = 0;
    if (size == 0 || nmem_blocks == 0 || fd <= 0) {
        res = TESTOS_STATUS_INVALID_ARG; 
        goto out; 
    }

    struct file_descriptor* descriptor = file_get_descriptor(fd);
    if (!descriptor) {
        res = TESTOS_STATUS_IO_ERROR;
        goto out; 
    }

    res = descriptor->filesystem->read(descriptor->disk, descriptor->private, size, nmem_blocks, (char*)ptr); 

out:
    return res; 
}

const int fseek(const int fd, const int offset, const FILE_SEEK_MODE whence)
{
    int res = 0; 
    struct file_descriptor* descriptor = file_get_descriptor(fd); 
    if (!descriptor) {
        res = -TESTOS_STATUS_IO_ERROR; 
        goto out; 
    }

    res = descriptor->filesystem->seek(descriptor->private, offset, whence); 

out:
    return res; 
}

const int fstat(const int fd, struct file_stat* stat)
{
    int res = 0; 
    struct file_descriptor* descriptor = file_get_descriptor(fd); 
    if (!descriptor) {
        res = -TESTOS_STATUS_IO_ERROR; 
        goto out; 
    }

    res = descriptor->filesystem->stat(descriptor->disk, descriptor->private, stat); 
out:
    return res; 
}

const int fclose(const int fd) 
{
    int res = 0; 
    struct file_descriptor* descriptor = file_get_descriptor(fd); 
    if (!descriptor) {
        res = -TESTOS_STATUS_IO_ERROR; 
        goto out; 
    }

    res = descriptor->filesystem->close(descriptor->private); 
    if (res == TESTOS_STATUS_OK) {
        file_free_descriptor(descriptor); 
    }
out:
    return res; 
}